package com.matrixxun.starry; import android.app.Activity; import android.content.Context; import android.os.SystemClock; import android.support.v4.view.MotionEventCompat; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.ViewGroup; import android.view.animation.Interpolator; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; public class PullToZoomListView extends ListView implements OnScrollListener { private static final int INVALID_VALUE = -1; private static float DEFAULT_MIN_SCALE = 1.0f; private static final String TAG = "PullToZoomListView"; int mActivePointerId = INVALID_VALUE; private FrameLayout mHeaderContainer; private ImageView mHeaderImage; private ImageView mShadow; private int mScreenHeight; private int mHeaderHeight; float mLastMotionY = INVALID_VALUE; float mLastScale = INVALID_VALUE; float mMaxScale = INVALID_VALUE; private OnScrollListener mOnScrollListener; private ScalingRunnable mScalingRunnable = new ScalingRunnable(); private boolean mScrollable = true; private boolean mShowHeaderImage = true; private boolean mZoomable = true; private static final Interpolator sInterpolator = new Interpolator() { public float getInterpolation(float t) { t -= DEFAULT_MIN_SCALE; return (t * t * t * t * t) + DEFAULT_MIN_SCALE; } }; class ScalingRunnable implements Runnable { long mDuration; boolean mIsFinished = true; float mScale; long mStartTime; public boolean isFinished() { return mIsFinished; } public void abortAnimation() { mIsFinished = true; } public void startAnimation(long duration) { mStartTime = SystemClock.currentThreadTimeMillis(); mDuration = duration; mScale = ((float) mHeaderContainer.getBottom()) / ((float) mHeaderHeight); mIsFinished = false; post(this); } public void run() { if (!mIsFinished && ((double) mScale) > 1.0d) { float scale = mScale - ((mScale - DEFAULT_MIN_SCALE) * sInterpolator.getInterpolation((((float) SystemClock.currentThreadTimeMillis()) - ((float) mStartTime)) / ((float) mDuration))); ViewGroup.LayoutParams params = mHeaderContainer.getLayoutParams(); if (scale <= DEFAULT_MIN_SCALE) { mIsFinished = true; params.height = mHeaderHeight; } else { params.height = (int) (((float) mHeaderHeight) * scale); } mHeaderContainer.setLayoutParams(params); post(this); } } } public PullToZoomListView(Context context) { super(context); } public PullToZoomListView(Context context, AttributeSet attrs) { super(context, attrs); } public PullToZoomListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onFinishInflate() { super.onFinishInflate(); init(getContext()); } private void init(Context context) { DisplayMetrics metrics = new DisplayMetrics(); ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metrics); mScreenHeight = metrics.heightPixels; mHeaderContainer = new FrameLayout(context); mHeaderImage = new ImageView(context); int width = metrics.widthPixels; setHeaderViewSize(width, (int) ((((float) width) / 16.0f) * 9.0f));//Screen Ratio = 16:9 mShadow = new ImageView(context); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.BOTTOM; mShadow.setLayoutParams(params); mHeaderContainer.addView(mHeaderImage); mHeaderContainer.addView(mShadow); addHeaderView(mHeaderContainer); super.setOnScrollListener(this); } public ImageView getHeaderView() { return mHeaderImage; } public void setShadow(int resId) { if (mShowHeaderImage) { mShadow.setBackgroundResource(resId); } } public void setHeaderViewSize(int width, int height) { if (mShowHeaderImage) { ViewGroup.LayoutParams params = mHeaderContainer.getLayoutParams(); if (params == null) { params = new AbsListView.LayoutParams(width, height); } params.width = width; params.height = height; mHeaderContainer.setLayoutParams(params); mHeaderHeight = height; } } public void setZoomable(boolean zoomable) { if (mShowHeaderImage) { mZoomable = zoomable; } } public boolean isZoomable() { return mZoomable; } public void setScrollable(boolean scrollable) { if (mShowHeaderImage) { mScrollable = scrollable; } } public boolean isScrollable() { return mScrollable; } public void hideHeaderImage() { mShowHeaderImage = false; mZoomable = false; mScrollable = false; removeHeaderView(mHeaderContainer); } protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mHeaderHeight == 0) { mHeaderHeight = mHeaderContainer.getHeight(); } } private void reset() { mActivePointerId = INVALID_VALUE; mLastMotionY = INVALID_VALUE; mMaxScale = INVALID_VALUE; mLastScale = INVALID_VALUE; } public boolean onInterceptTouchEvent(MotionEvent ev) { if (!mZoomable) { return super.onInterceptTouchEvent(ev); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mActivePointerId = ev.getPointerId(0); mMaxScale = ((float) mScreenHeight) / ((float) mHeaderHeight); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: reset(); break; case MotionEvent.ACTION_POINTER_DOWN: mActivePointerId = ev.getPointerId(ev.getActionIndex()); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } return super.onInterceptTouchEvent(ev); } public boolean onTouchEvent(MotionEvent ev) { if (!mZoomable) { return super.onTouchEvent(ev); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScalingRunnable.mIsFinished) { mScalingRunnable.abortAnimation(); } mLastMotionY = ev.getY(); mActivePointerId = ev.getPointerId(0); mMaxScale = ((float) mScreenHeight) / ((float) mHeaderHeight); mLastScale = ((float) mHeaderContainer.getBottom()) / ((float) mHeaderHeight); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: reset(); endScaling(); break; case MotionEvent.ACTION_MOVE: int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex != INVALID_VALUE) { if (mLastMotionY == INVALID_VALUE) { mLastMotionY = ev.getY(activePointerIndex); } if (mHeaderContainer.getBottom() < mHeaderHeight) { mLastMotionY = ev.getY(activePointerIndex); break; } ViewGroup.LayoutParams params = mHeaderContainer.getLayoutParams(); float scale = ((((((float) mHeaderContainer.getBottom()) + (ev.getY(activePointerIndex) - mLastMotionY)) / ((float) mHeaderHeight)) - mLastScale) / 2.0f) + mLastScale; if (((double) mLastScale) > 1.0d || scale >= mLastScale) { mLastScale = Math.min(Math.max(scale, DEFAULT_MIN_SCALE), mMaxScale); params.height = (int) (((float) mHeaderHeight) * mLastScale); if (params.height < mScreenHeight) { mHeaderContainer.setLayoutParams(params); } mLastMotionY = ev.getY(activePointerIndex); return super.onTouchEvent(ev); } params.height = mHeaderHeight; mHeaderContainer.setLayoutParams(params); return super.onTouchEvent(ev); } break; case MotionEvent.ACTION_POINTER_DOWN: int index = ev.getActionIndex(); mLastMotionY = ev.getY(index); mActivePointerId = ev.getPointerId(index); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId)); break; } return super.onTouchEvent(ev); } private void onSecondaryPointerUp(MotionEvent ev) { int pointerIndex = (ev.getAction() & MotionEventCompat.ACTION_POINTER_INDEX_MASK) >> 8; if (ev.getPointerId(pointerIndex) == mActivePointerId) { int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastMotionY = ev.getY(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); } } private void endScaling() { if (mHeaderContainer.getBottom() >= mHeaderHeight) { mScalingRunnable.startAnimation(180); } } public void onScrollStateChanged(AbsListView view, int scrollState) { if (mOnScrollListener != null) { mOnScrollListener.onScrollStateChanged(view, scrollState); } } public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (mScrollable) { float scrollY = (float) (mHeaderHeight - mHeaderContainer.getBottom()); if (scrollY > 0.0f && scrollY < ((float) mHeaderHeight)) { mHeaderImage.scrollTo(0, -((int) (((double) scrollY) * 0.65d))); } else if (mHeaderImage.getScrollY() != 0) { mHeaderImage.scrollTo(0, 0); } } if (mOnScrollListener != null) { mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } public void setOnScrollListener(OnScrollListener l) { mOnScrollListener = l; } }